#!/usr/bin/env python
# -*- coding: utf-8 -*-
import xml.dom.minidom as dom
"""
Title: Pickle Python Objects to a DOM tree and vice versa
Submitter: Uwe Schmitt (other recipes)
Last Updated: 2004/12/04
Version no: 1.1 
url:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/355487

manatlan modifications :
 - unicode aware
 - attribute index removed for the list
 - and 2 simple fct : data2node & node2data
"""

class PickleMeToXML(object): pass

# helper function

def getType(obj):
    """ generates string representation of class of obj 
        discarding decoration """
    return str(obj.__class__).split("'")[1].split(".")[-1]

_easyToPickle = [ "int", "float" ]

_isCallable = lambda o: hasattr(o, "__call__")

# 
#   pickling 
# 

def _pickleDictItems(root, node, fabric):
    for key, value in root.items():
        tempnode = fabric.createElement("item")
        tempnode.appendChild(pickle(key, fabric, "key"))
        tempnode.appendChild(pickle(value, fabric, "value"))
        node.appendChild(tempnode)

def _pickleListItems(root, node, fabric):
    for idx, obj in enumerate(root):
        tempnode = pickle(obj, fabric, "item")
        #~ tempnode.attributes["index"] = str(idx) #manatlan
        node.appendChild(tempnode)

_pickleTupleItems = _pickleListItems

def pickle(root, fabric, elementName="root"):

    node = fabric.createElement(elementName)
    typeStr = getType(root)
    node.attributes["type"]=typeStr

    if isinstance(root, PickleMeToXML):
        node = _pickleObjectWithAttributes(node, root, fabric, elementName)
    elif typeStr in _easyToPickle:
        node.appendChild(fabric.createTextNode(str(root)))
    elif isinstance(root, dict):
        _pickleDictItems(root, node, fabric)
    elif isinstance(root, list):
        _pickleListItems(root, node, fabric)
    elif isinstance(root, tuple):
        _pickleTupleItems(root, node, fabric)
    elif isinstance(root, unicode) or isinstance(root, str):
        node.attributes["type"]="string"
        
        if isinstance(root, str):
            val = root.decode("utf_8")
        else:
            val = root
            
        #~ rep = repr(val)[2:-1] # without le "u'"+"'"
        node.appendChild(fabric.createTextNode( val ))  #en unicode !!!!
    else:
        # fallback handler
        print "------------****---------------",root
        node.appendChild(fabric.createTextNode(repr(root)))
    return node

def _pickleObjectWithAttributes(node, root, fabric, elementName):

    # pickle all members or just a subset ??? 
    if hasattr(root, "__pickle_to_xml__"):
        attributesToPickle = root.__pickle_to_xml__
    else:
        # avoid members which are python internal
        attributesToPickle = [ name for name in dir(root) if not name.startswith("__") ]

    for name in attributesToPickle: 
        obj = getattr(root, name)

        # do not pickle member functions
        if _isCallable(obj): continue

        # is there some special encoding method ??
        if hasattr(root, "_xml_encode_%s" % name):
            value = getattr(root, "_xml_encode_%s" % name)()
            node.appendChild(fabric.createTextNode(value))
        else:
            node.appendChild(pickle(obj, fabric, name))
    return node

#
#   unpickling 
#

# helper functions

def _getElementChilds(node, doLower = 1):
    """ returns list of (tagname, element) for all element childs of node """

    dolow = doLower and (lambda x:x.lower()) or (lambda x:x)
    return [ (dolow(no.tagName), no) for no in node.childNodes if no.nodeType != no.TEXT_NODE ]

def _getText(nodelist):
    """ returns collected and stripped text of textnodes among nodes in nodelist """
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc.strip()

# main unpickle function

def unpickle(node):

    typeName= node.attributes["type"].value

    if typeName in _easyToPickle: 
        initValue = _getText(node.childNodes)
        value = eval("%s(%r)" % (typeName, initValue))
        return value 
    elif typeName=="tuple":
        return _unpickleTuple(node)
    elif typeName=="list":
        return _unpickleList(node)
    elif typeName=="dict":
        return _unpickleDict(node)
    elif typeName=="string":
        #~ obj = eval("""unicode(u'%s')""" % _getText(node.childNodes))
        obj = _getText(node.childNodes) # unicode !
        return obj
    else:
        obj = eval("object.__new__(%s)" % typeName)
        for name, element in _getElementChilds(node):
            setattr(obj, name, unpickle(element))
        return obj
        
class XMLUnpicklingException(Exception): pass

def _unpickleList(node):
    li = []
    # collect entries, you can not assume that the
    # members of the list appear in the right order !
    idx=0 #manatlan
    for name, element in _getElementChilds(node):
        if name != "item":
            raise XMLUnpicklingException()
        #~ idx = int(element.attributes["index"].value)
        obj = unpickle(element)
        li.append((idx, obj))
        idx+=1  #manatlan

    # rebuild list with right order
    li.sort()
    return [ item[1] for item in li ]

def _unpickleTuple(node):
    return tuple(_unpickleList(node))

def _unpickleDict(node):
    dd = dict()
    for name, element in _getElementChilds(node):
        if name != "item":
            raise XMLUnpicklingException()
        childList = _getElementChilds(element)
        if len(childList) != 2:
            raise XMLUnpicklingException()
        for name, element in childList:
            if name=="key":
                key = unpickle(element)
            elif name=="value":
                value = unpickle(element)
        dd[key]=value
    return dd


class Data(PickleMeToXML): pass

def data2node( data ):
    if data!=None:
        obj = Data()
        obj.value = data
        node = pickle(root=obj, fabric=dom.Document())
        return node.childNodes[0]

def node2data( nod ):
    node = nod.cloneNode(True)
    doc = node.ownerDocument
    n = doc.createElement("root")
    n.setAttributeNode(doc.createAttribute("type")) 
    n.setAttribute("type","Data")
    n.appendChild(node)
    obj=unpickle(n)
    return obj.value 
    
if __name__=="__main__":

    v=u"héllo","hél",9,{1:12},({ u"a":123,u"béé":566},123)
    v=None
    node = data2node(v)
    
    print node.toprettyxml().encode("utf_8")
    #~ #===============================================
    
    #~ val = node2data( node )
    #~ print val[0].encode("utf_8")
    #~ print val[1].encode("utf_8")
    